// $Id: dump.c 58 2011-04-23 07:35:46Z cedric.shih@gmail.com $
/*
 * Copyright (C) 2011 Cedric Shih <cedric.shih@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <arpa/inet.h>

#include "nio.h"

#include "log.h"
#include "socket.h"
#include "buffer.h"
#include "handler.h"
#include "channel.h"

#include "dump.h"

struct niohandler niodump_handler = {
		niodump_handler_setup,
		niodump_handler_teardown,
		niodump_handler_handle_upstream,
		niodump_handler_handle_downstream,
		NULL,
};

struct niodump_handler *niodump_handler_new(FILE *file)
{
	struct niodump_handler *handler;

	niodebug("constructing");

	if (!(handler = calloc(1, sizeof(*handler)))) {
		nioerror("failed to calloc");
		goto exception;
	}

	handler->file = file;
	return handler;

exception:
	niodump_handler_free(handler);
	return NULL;
}

void niodump_handler_free(void *obj)
{
	struct niodump_handler *handler = obj;

	niodebug("destructing: %p", handler);

	if (!handler) {
		return;
	}

	free(handler);
}

int niodump_handler_setup(void *obj, const char *name)
{
	int rc;
	struct niodump_handler *handler = obj;

	if (!handler || !name) {
		nioerror("null argument(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	niodebug("setting up: %s", name);

	handler->name = name;
	rc = 0;

finally:
	return rc;
}

int niodump_handler_teardown(void *obj)
{
	int rc;
	struct niodump_handler *handler = obj;

	if (!handler) {
		nioerror("null argument(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	niodebug("tearing down: %s", handler->name);

	handler->name = NULL;
	rc = 0;

finally:
	return rc;
}

int niodump_canonical_hexdump(FILE *fileout, const char *title,
		const char *address, unsigned short port,
		struct niobuffer *buffer)
{
	int rc;
	long int addr;
	unsigned char *b;
	unsigned char buf[16];
	long int cnt;
	long int cnt2;
	long m;
	long n;
	/*
	 Dump the file contents.
	 */
	fprintf(fileout, "================================================================================\n");
	fprintf(fileout, "BEGIN OF DUMP: %s %s:%d\n", title, address, port);
	fprintf(fileout, "================================================================================\n");
	fprintf(fileout, "   Address                  Hexadecimal values                  Printable\n");
	fprintf(fileout, "-------------- -----------------------------------------------  ----------------\n");

	addr = 0;

	while (niobuffer_remaining(buffer) > 0) {
		cnt = sizeof(buf);

		if (niobuffer_remaining(buffer) < cnt) {
			cnt = niobuffer_remaining(buffer);
		}

		if ((rc = niobuffer_gets(buffer, buf, cnt))) {
			nioerror("failed to read %ld bytes from buffer: %s",
					cnt, nio_strerror(rc));
			goto finally;
		}

		b = buf;
		/*
		 Print the address in decimal and hexadecimal.
		 */
		fprintf(fileout, "%5d %08lx ", (int) addr, addr);
		addr = addr + sizeof(buf);
		/*
		 Print sizeof(buf) data items, in pairs, in hexadecimal.
		 */
		cnt2 = 0;
		for (m = 0; m < sizeof(buf); m++) {
			cnt2 = cnt2 + 1;
			if (cnt2 <= cnt) {
				fprintf(fileout, "%02x", *b++);
			} else {
				fprintf(fileout, "  ");
			}
			fprintf(fileout, " ");
		}
		/*
		 Print the printable characters, or a period if unprintable.
		 */
//		fprintf(fileout, " ");
		cnt2 = 0;
		for (n = 0; n < sizeof(buf); n++) {
			cnt2 = cnt2 + 1;
			if (cnt2 <= cnt) {
				if ((buf[n] < 32) || (buf[n] > 126)) {
					fprintf(fileout, "%c", '.');
				} else {
					fprintf(fileout, "%c", buf[n]);
				}
			}
		}
		fprintf(fileout, "\n");
	}

	rc = 0;

finally:
	fprintf(fileout, "================================================================================\n");
	fprintf(fileout, "END OF DUMP: %s %s:%d\n", title, address, port);
	fprintf(fileout, "================================================================================\n");
	return rc;
}

int niodump_handler_handle_upstream(void *obj,
		struct niochannel *channel, void *message)
{
	int rc;
	size_t pos;
	struct niodump_handler *handler = obj;
	struct niobuffer *buffer = message;

	if (!handler || !message || !channel) {
		nioerror("null argument(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	niodebug("handling upstream: %s", handler->name);

	pos = niobuffer_position(buffer);

	if ((rc = niodump_canonical_hexdump(handler->file, "UPSTREAM",
			inet_ntoa(channel->socket->address),
			channel->socket->port, buffer))) {
		nioerror("failed to dump canonical hex: %s", nio_strerror(rc));
		goto finally;
	}

	if ((rc = niobuffer_set_position(buffer, pos))) {
		nioerror("failed to restore position: %s", nio_strerror(rc));
		goto finally;
	}

	if ((rc = niochannel_send_upstream(channel, handler->name, message))) {
		nioerror("failed to send upstream: %s", nio_strerror(rc));
		goto finally;
	}

	rc = 0;

finally:
	return rc;
}

int niodump_handler_handle_downstream(void *obj,
		struct niochannel *channel, void *message)
{
	int rc;
	size_t pos;
	struct niodump_handler *handler = obj;
	struct niobuffer *buffer = message;

	if (!handler || !channel || !message) {
		nioerror("null argument(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	niodebug("handling downstream: %s", handler->name);

	pos = niobuffer_position(buffer);

	if ((rc = niodump_canonical_hexdump(handler->file, "DOWNSTREAM",
			inet_ntoa(channel->socket->address),
			channel->socket->port, buffer))) {
		nioerror("failed to dump canonical hex: %s", nio_strerror(rc));
		goto finally;
	}

	if ((rc = niobuffer_set_position(buffer, pos))) {
		nioerror("failed to restore position: %s", nio_strerror(rc));
		goto finally;
	}

	if ((rc = niochannel_send_downstream(channel, handler->name, message))) {
		nioerror("failed to send downstream: %s", nio_strerror(rc));
		goto finally;
	}

	rc = 0;

finally:
	return rc;
}
